package xj.graph2d.shapes.block;

import static xj.graph2d.DrawShapeConstants.PointOnIcon;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.FontMap;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.RectangleShape;
import xj.graph2d.TextMarker;
import xj.graph2d.TextShape;
import xj.graph2d.shapes.BarShape;
import xj.graph2d.util.DrawingUtilities;

/*
 * BlockShape: adjust block size
 * add/remove shape: DrawShapeBase.adjustParentLayout()
 *    -> forall parent:    layoutComponents();
 *       case DrawShapeBase: fitCompartments(boolean)
 *       case GraphNodeBase:  positionSubNodes() .... 
 *       case CompartmentalRoundRectangleShape: fitCompartments()
 *       case TitledShape: 
 */

public class BlockShape 
  extends RectangleShape
  implements StatementShape { 

  // Block type 
  public static final int DEFAULT_BLOCK      = 0; 

  public static final int WHILE_BLOCK        = 101; 
  public static final int DO_WHILE_BLOCK     = 102; 
  public static final int FOR_BLOCK          = 103; 

  public static final int IF_BLOCK           = 110; 
  public static final int TRUE_BRANCH        = 111;
  public static final int FALSE_BRANCH       = 112;

  public static final int SWITCH_BLOCK       = 120; 
  public static final int CASE_BLOCK         = 121; 
  public static final int CASE_DEFAULT       = 122; 

  public static final int TRY_BLOCK          = 130;
  public static final int CATCH_LIST         = 131;
  public static final int CATCH_BLOCK        = 132;
  public static final int CATCH_FINALLY      = 133;

  public static final int IMPORT_LIST        = 140;  
  public static final int CLASS_BLOCK        = 141; 
  public static final int ENUM_BLOCK         = 142; 

  public static final int ATTRIBUTE_LIST     = 150; 
  public static final int CONSTANT_LIST      = 151; 
  public static final int ENUM_LIST          = 152; 
  public static final int STATIC_FIELD_LIST  = 153; 

  public static final int CONSTRUCTOR_LIST   = 154; 
  public static final int METHOD_LIST        = 155; 
  public static final int STATIC_METHOD_LIST = 156; 
  public static final int INNER_CLASS_LIST   = 157; 

  public static final int ACTION_LIST        = 158; 

  public static final int METHOD_BLOCK       = 160; 
  public static final int PARAMETER_LIST     = 161; 
  public static final int METHOD_BODY        = 162;  
  public static final int METHOD_PRE         = 163;  
  public static final int METHOD_POST        = 164;  
  public static final int CONSTRUCTOR_BLOCK  = 165;  

  public static final int INIT_BLOCK         = 170; 
  public static final int STATIC_INIT_BLOCK  = 171; 

  // Roles for conditions and expressions 
  public static final int CONDITION          = 1000;
  public static final int EXPRESSION         = 1001;
  public static final int INITIALIZATION     = 1002;
  public static final int INCREMENTATION     = 1003;
  public static final int ITERABLE           = 1004;
  public static final int NAME               = 1005;
  public static final int TYPE               = 1006;
  public static final int PARAMETER          = 1007;
  public static final int ATTRIBUTE          = 1008;
  public static final int METHOD             = 1009;
  public static final int EXCEPTION          = 1010;

  public BlockShape() { 
    this(false);
  }

  public BlockShape(boolean createComp) { 
    super(true, createComp);
    setAllowRotation(false);
    setSymetricResize(false);
    setAcceptSubshape(true);
    setAcceptLabel(false);
    setFitted(true);
  }

  public BlockShape(float x1, float y1, float x2, float y2) { 
    this(x1, y1, x2, y2, false);
  }

  public BlockShape(float x1, float y1, float x2, float y2,
		    boolean createComp) { 
    super(x1, y1, x2, y2, true, createComp);
    setAllowRotation(false);
    setSymetricResize(false);
    setAcceptSubshape(true);
    setAcceptLabel(false);
    setFitted(true);
  }

  @Override
  public DrawShape makeInstance() {
    return new BlockShape(comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof BlockShape) {
      BlockShape b = (BlockShape) s;
      b.blockType = blockType;

      b.expanded = expanded;
      b.showExpansionBar = showExpansionBar;
      b.showSideBar = showSideBar;
      b.showHeader = showHeader;
      b.showFooter = showFooter;
      b.showAddButton = showAddButton;
      b.sideBarWidth = sideBarWidth; 
      b.headerHeight = headerHeight; 
      b.footerHeight = footerHeight;
      b.headerText = headerText; 
      b.footerText = footerText;
      b.closable = closable; 
      b.closed = closed;
      b.modifiers =  modifiers;

      b.topMargin = topMargin;
      b.bottomMargin = bottomMargin;
      b.headerColor = headerColor;
      b.footerColor = footerColor;
      b.sideBarColor = sideBarColor;
      b.textColor = textColor;
    }
  }

  @Override
  public void drawSubShapes(Graphics2D g2, int mode, DrawShape selectedShape,
			    Set<DrawShape> selectionSet) {
    drawSubShapes(g2, mode, selectedShape, selectionSet, true);
  }

  @Override
  public void draw(Graphics2D g2) { 
    super.draw(g2);

    float left = (float) Math.min(p1.getX(), p2.getX());
    float top = (float) Math.min(p1.getY(), p2.getY());
    float width = (float) Math.abs(p1.getX() - p2.getX());
    float height = (float) Math.abs(p1.getY() - p2.getY());

    Color oldColor = null;
    Color newColor = null;
    if (attr != null) {
      newColor = attr.getPenColor();
    } else {
      newColor = DrawAttr.getDefaultPenColor();
    }
    oldColor = g2.getColor();

    if (newColor != null) {
      g2.setColor(newColor);
    }

    // draw expansion bar
    if (!closed && 
	showExpansionBar) { 
      float x0 = left + 2;
      float y0 = top + 2;
      float w0 = expansionBarWidth - 2; 
      if (showHeader) { 
	y0 += headerHeight; 
      }
      //draw [-] icon 
      g2.drawRect((int) x0, (int) y0, (int) w0, (int) w0);
      g2.drawLine((int) x0, (int) (y0 + w0 / 2), (int) (x0 + w0), (int) (y0 + w0 / 2));
      if (!expanded) { 
	// draw [+] icon
	g2.drawLine((int) (x0 + w0 / 2), (int) y0, (int) (x0 + w0 / 2), (int) (y0 + w0));
	// draw collapsed statement
	int x1 = (int) x0 + GAP + expansionBarWidth;
	int y1 = (int) top + GAP;
	int w1 = (int) width - GAP * 2 - expansionBarWidth - 7;
	/*
	int h1 = (int) height - 2 * GAP - 7;
	if (showSideBar) { 
	  w1 -= sideBarWidth;
	}
	if (showHeader) { 
	  y1 += headerHeight;
	  h1 -= headerHeight; 
	}
	if (showFooter) { 
	  h1 -= footerHeight;
	}
	*/
	int h1 = MIN_HEIGHT - 7;
	if (showSideBar) { 
	  w1 -= sideBarWidth;
	}
	if (showHeader) { 
	  y1 += headerHeight;
	}

	Shape shape = new RoundRectangle2D.Float(x1 + 4, y1 + 4, w1, h1, 10, 10); 
	DrawingUtilities.drawDropShadow(g2, shape, true, shadowWidth);
	drawShape3(shape, g2);
      } else { 
	int n = getStatementCount();
	if (n > 1) { 
	  StatementShape s = getFirstStatement(); 
	  int y1 = (int) s.getCenterPosition().getY();
	  s = getLastStatement(); 
	  int y2 = (int) s.getCenterPosition().getY();
	  // v-line
	  g2.drawLine((int) (x0 + w0 / 2), (int) (y0 + w0), (int) (x0 + w0 / 2), y2);
	  // h-line 
	  g2.drawLine((int) (x0 + w0 / 2), y1, (int) (x0 + w0) + GAP, y1);
	  g2.drawLine((int) (x0 + w0 / 2), y2, (int) (x0 + w0) + GAP, y2);
	}
      }
    }

    // draw header
    if (showHeader) { 
      int x1 = (int) left + 1;
      int y1 = (int) top + 1;
      int w1 = (int) width - 1;
      int h1 = headerHeight - 1;      
      int tx = x1 + 3; // text position 
      
      if (closable) { 
	float h0 = h1 - 4;
	tx += (h0 + 2); 
	if (closed) { 	
	  DrawingUtilities.fillTriangle(g2, x1 + 2, y1 + 2, 
					x1 + 2, y1 + 2 + h0, 
					x1 + 2 + h0, y1 + 2 + h0 / 2);
	} else {
	  DrawingUtilities.fillTriangle(g2, x1 + 2, y1 + 2, 
					x1 + 2 + h0, y1 + 2, 
					x1 + 2 + h0 / 2, y1 + 2 + h0);
	}
      }

      if (headerText != null) {
	Font font = getHeaderFont(); 
	g2.setFont(font);
	g2.setColor(textColor); 
	int y2 = y1 + headerHeight - 4 - (headerHeight - font.getSize() - 2) / 2;
	g2.drawString(headerText, tx, y2);

	Color hcolor = getHeaderColor(); 
	g2.setColor(hcolor);
	switch (blockType) { 
	case METHOD_BLOCK:
	  g2.fillRect(x1, y1, w1, h1);

	  DrawShape name = findInRole(NAME);
	  //DrawShape type = findInRole(TYPE);
	  if (name != null) { 
	    Rectangle2D b1 = name.getBounds();
	    int x2 = (int) (b1.getX() + b1.getWidth());
	    g2.setColor(textColor); 
	    g2.drawString(":", x2 + 2, y2);
	  }
	  break;
	case FOR_BLOCK: 
	case CATCH_BLOCK:
	case SWITCH_BLOCK:
	case CASE_BLOCK:
	case IF_BLOCK:
	case WHILE_BLOCK:
	case DO_WHILE_BLOCK:
	  // othre block draw left and right stubs 
	  g2.fill(makeLeftStub(x1, y1, w1, h1));
	  g2.fill(makeRightStub(x1, y1, w1, h1));	  
	default:
	  // fill the entire header 
	  g2.fillRect(x1, y1, w1, h1);
	}
      } else { 
	g2.setColor(headerColor);
	g2.fillRect(x1, y1, w1, h1);
      }
      g2.setColor(newColor);
      g2.drawLine(x1 - 1, y1 + h1, x1 + w1, y1 + h1);

      if (showAddButton) { 
	int bw = 8;
	int by = y1 + (h1 - bw) / 2;
	int bx = x1 + w1 - bw - (h1 - bw) / 2;
	g2.setColor(newColor);
	g2.fillRect(bx, by, bw, bw);
	g2.setColor(Color.white);
	g2.drawRect(bx, by, bw, bw);
	g2.drawLine(bx, by + bw / 2, bx + bw, by + bw / 2);
	g2.drawLine(bx + bw / 2, by, bx + bw / 2, by + bw);
      }

    }

    // draw footer
    if (!closed && 
	showFooter) { 
      int x1 = (int) left + 1;
      int y1 = (int) (top + height) - footerHeight + 1;
      int w1 = (int) width - 1;
      int h1 = footerHeight - 1;
      int tx = x1 + 3; // text position 

      if (footerText != null) { 
	Font font = getHeaderFont(); 
	g2.setFont(font);
	g2.setColor(textColor); 
	int y2 = y1 + footerHeight - 4 - (footerHeight - font.getSize() - 2) / 2;
	g2.drawString(footerText, tx, y2);

	g2.setColor(footerColor);
	g2.fill(makeLeftStub(x1, y1, w1, h1));
	g2.fill(makeRightStub(x1, y1, w1, h1));
      } else { 
	g2.setColor(footerColor);
	g2.fillRect(x1, y1, w1, h1);
      }
      g2.setColor(newColor);
      g2.drawLine(x1 - 1, y1 - 1, x1 + w1, y1 - 1);
    }

    // draw side bar
    if (!closed && 
	showSideBar) { 
      int x1 = (int) (left + width) - sideBarWidth + 1; 
      int y1 = (int) top + 1; 
      int w1 = sideBarWidth - 1;
      int h1 = (int) height - 1; 
      if (showHeader) { 
	y1 += headerHeight; 
	h1 -= headerHeight; 
      }
      if (showFooter) { 
	h1 -= footerHeight; 
      }     
      Color scolor = getSideBarColor(); 
      g2.setColor(scolor);
      g2.fillRect(x1, y1, w1, h1);
    }

    // DEBUG: walkSubshapes();    

    g2.setColor(oldColor);
  }

  // reposition nodes afte a node is removed 
  @Override
  public boolean removeShape(DrawShape s, boolean disconnect) {
    //System.out.println("BlockShape.removeShape()");
    if (super.removeShape(s, disconnect)) {
      if (s instanceof StatementShape) { 
	positionSubNodes();      
	adjustParentLayout();  
      }
      return true; 
    }
    return false; 
  }
  
  @Override
  public boolean removeShape(DrawShape s) { 
    //System.out.println("BlockShape.removeShape()");
    if (super.removeShape(s)) { 
      if (s instanceof StatementShape) { 
	positionSubNodes();  
	adjustParentLayout();  
      }
      return true; 
    }
    return false; 
  }

  @Override
  public DrawShape removeShape(int i) { 
    //System.out.println("BlockShape.removeShape()");
    DrawShape s = super.removeShape(i);
    if (s instanceof StatementShape) { 
      positionSubNodes();    
      adjustParentLayout();  
    }
    return s;
  }

  public int getStatementCount() { 
    int count = 0; 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s instanceof StatementShape) {
	  count++; 
	}
      }
    }
    return count; 
  }

  public StatementShape getFirstStatement() { 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s instanceof StatementShape) {
	  return (StatementShape) s; 
	}
      }
    }
    return null; 
  } 

  public StatementShape getLastStatement() { 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = n - 1; i >= 0; i--) {
	DrawShape s = subshapes.get(i);
	if (s instanceof StatementShape) {
	  return (StatementShape) s; 
	}
      }
    }
    return null; 
  } 

  public java.util.List<StatementShape> getAllStatements() { 
    java.util.List<StatementShape> stmtList = null; 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s instanceof StatementShape) {
	  if (stmtList == null) { 
	    stmtList = new ArrayList<StatementShape>();
	  }
	  stmtList.add((StatementShape) s); 
	}
      }
    }
    return stmtList; 
  }

  public void removeAllStatements() { 
    if (subshapes != null &&
	subshapes.size() > 0) { 
      Iterator<DrawShape> iter = subshapes.iterator(); 
      while (iter.hasNext()) { 
	DrawShape s = iter.next();
	if (s instanceof StatementShape) {
	  iter.remove();
	}
      }
    }
  }

  public int getModifiers() { 
    return modifiers; 
  }

  public void setModifiers(int modifiers) { 
    this.modifiers =  modifiers;
  }

  public int getMinWidth() { 
    int minw = MIN_WIDTH;
    int dw = 2 * GAP;
    if (showExpansionBar) {
      dw += expansionBarWidth;
    }
    if (showSideBar) {
      dw += sideBarWidth;
    }
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s instanceof StatementShape) {
	  StatementShape stmt = (StatementShape) s;
	  minw = Math.max(minw, stmt.getMinWidth() + dw);
	}
      }
    }
    return minw;
  }

  @Override
  public int addShape(DrawShape s, boolean select) {
    if (s != null) { 
      if (blockType == METHOD_LIST ||
	  blockType == STATIC_METHOD_LIST) { 
	if (s instanceof MethodDeclShape) { 
	  MethodDeclShape m = (MethodDeclShape) s;
	  int mtype;
	  if (blockType == METHOD_LIST) { 
	    mtype = SimpleStatementShape.METHOD_DECL;
	  } else {
	    mtype = SimpleStatementShape.STATIC_METHOD_DECL;
	  }
	  m.setStatementType(mtype);
	}
      } else if (blockType == ATTRIBUTE_LIST ||
		 blockType == STATIC_FIELD_LIST ||
		 blockType == CONSTANT_LIST) { 
	if (s instanceof AttributeShape) {
	  AttributeShape a = (AttributeShape) s;
	  int atype;
	  if (blockType == ATTRIBUTE_LIST) { 
	    atype = SimpleStatementShape.ATTRIBUTE;
	  } else if (blockType == STATIC_FIELD_LIST) { 
	    atype = SimpleStatementShape.STATIC_FIELD;
	  } else { 
	    atype = SimpleStatementShape.CONSTANT;
	  }
	  a.setStatementType(atype);
	} else if (blockType != CONSTANT_LIST && 
		   s instanceof BlockShape) { 
	  BlockShape b = (BlockShape) s;
	  if (isInitBlock(b)) { 
	    if (blockType == ATTRIBUTE_LIST) { 
	      b.setBlockType(INIT_BLOCK); 
	    } else { 
	      b.setBlockType(STATIC_INIT_BLOCK); 
	    }
	  }
	}
      }
      int result = super.addShape(s, select);
      if (s instanceof StatementShape) { 
	positionSubNodes();    
	adjustParentLayout();  
      }
      return result;
    }
    return -1; 
  }

  @Override
  public void positionSubNodes() {
    positionSubNodes(fitted);
  }

  public void positionSubNodes(boolean fit) {
    //System.out.println("BlockShape.positionSubNode() " + bounds + " closed=" + closed + " fit=" + fit);   

    positionExpressions();

    if (fit &&
	(subshapes != null && subshapes.size() > 0)) {
      float left = (float) Math.min(p1.getX(), p2.getX());
      float top = (float) Math.min(p1.getY(), p2.getY());
      float width = (float) Math.abs(p1.getX() - p2.getX());
      int minw = getMinWidth();
      if (width < minw) { 
	width = minw;
      }

      if (closed) { 
	float h = 10;
	if (showHeader) { 
	  h = headerHeight;
	}

	//System.out.println("BlockShape.positionSubNodes() setEnds " + 
	//	   left + " " +  top + " " + (left + width) + " " + (top + h));

	if (fit) { 
	  setEnds(left, top, left + width, top + h);
	}
      } else { 
	layoutStatement(top, left, width, fit); 
      }
      //adjustParentLayout();
    }
  }

  // pos is the insertion location of the new node
  @Override
  public boolean positionSubNode(GraphNodeBase node,  Point2D pos) {
    //System.out.println("BlockShape.positionSubNode(node, pos) " + node + " " + pos);
    boolean changed = positionExpressions();      

    if (node != null && 
	subshapes != null) {

      if (isClosed()) { 
	open();
      } else if (!isExpanded()) { 
	expand();
      }

      if (node instanceof StatementShape) { 
	subshapes.remove(node);
	int n = subshapes.size();
	if (isDefaultCaseBlock(node)) { 
	  DrawShape s0 = findDefaultCaseBlock();
	  if (s0 != null) { 
	    removeShape(s0);
	    changed = true; 
	  }	
	  subshapes.add(node);      
	} else if (isFinallyBlock(node)) {
 	  DrawShape s0 = findFinallyBlock();
	  if (s0 != null) { 
	    removeShape(s0);
	    changed = true; 
	  }	
	  subshapes.add(node);      
	} else { 	   
	  float y0 = (float) node.getCenterPosition().getY(); 
	  if (pos != null && 
	      node.getBounds().contains(pos.getX(), pos.getY())) { 
	    y0 = (float) pos.getY();
	  }	  
	  //System.out.println("BlockShape.positionSubNode(node, pos) y0=" + y0);
	
	  int i = 0; 
	  for (; i < n; i++) {
	    DrawShape s = subshapes.get(i);
	    if (s instanceof StatementShape) { 
	      if (isDefaultCaseBlock(s) ||
		  isFinallyBlock(s)) { 
		break; 
	      }
	      float y = (float) s.getCenterPosition().getY();	
	      //System.out.println("BlockShape.positionSubNode(node, pos) y[" + i + "]=" + y);	   
	      if (y > y0) { 
		break; 
	      }
	    }
	  }
	  subshapes.add(i, node);   
	}
      } else if (node instanceof BarShape) { 
	int role = node.getRole();
	if (blockType == WHILE_BLOCK || 
	    blockType == DO_WHILE_BLOCK) { 
	  if (role == CONDITION) { 
	    DrawShape s0 = findInRole(CONDITION);
	    if (s0 != null && s0 != node) { 
	      removeShape(s0); // update doc tree
	      changed = true; 
	    }
	  }
	} else if (blockType == FOR_BLOCK) { 
	  if (role == INITIALIZATION ||
	      role == CONDITION ||
	      role == INCREMENTATION ||
	      role == ITERABLE) {
	    DrawShape s0 = findInRole(role);
	    if (s0 != null && s0 != node) { 
	      removeShape(s0); // update doc tree
	      changed = true; 
	    }
	  }
	} else if (blockType == SWITCH_BLOCK) {
	  if (role == EXPRESSION) { 
	    DrawShape s0 = findInRole(EXPRESSION);
	    if (s0 != null && s0 != node) { 
	      removeShape(s0); // update doc tree
	      changed = true; 
	    }
	  }
	} else if (blockType == CASE_BLOCK) {
	  ;
	} else if (blockType == CATCH_BLOCK) {
	  if (role == EXCEPTION) { 
	    DrawShape s0 = findInRole(EXCEPTION);
	    if (s0 != null && s0 != node) { 
	      removeShape(s0); // update doc tree
	      changed = true; 
	    }
	  }
	}
      }
      //System.out.println("BlockShape.positionSubNode(node, pos) insert at:" + i);

      float left = (float) Math.min(p1.getX(), p2.getX());
      float top = (float) Math.min(p1.getY(), p2.getY());
      float width = (float) Math.abs(p1.getX() - p2.getX());
      int minw = getMinWidth();
      if (width < minw) { 
	width = minw;
      }

      layoutStatement(top, left, width);
      //adjustParentLayout();
    }
    return changed;
  }

  protected boolean positionExpressions() { 
    boolean changed = false; 
    float left = (float) Math.min(p1.getX(), p2.getX());
    float top = (float) Math.min(p1.getY(), p2.getY());
    float width = (float) Math.abs(p1.getX() - p2.getX());
    float height = (float) Math.abs(p1.getY() - p2.getY());
    if (showHeader) { 
      int x1 = (int) left + 1;
      int y1 = (int) top + 1;
      int w1 = (int) width - 1;
      int h1 = headerHeight - 1;
      if (blockType == WHILE_BLOCK || 
	  blockType == IF_BLOCK) {
	DrawShape s = findInRole(CONDITION);
	if (s instanceof BarShape) { 
	  BarShape cond = (BarShape) s;
	  float x = x1 + getLeftStubWidth() - STUB_END_SIZE / 2;
	  float w = w1 - rightStubWidth - getLeftStubWidth() + STUB_END_SIZE; 
	  int h = (int) cond.getPreferredHeight();
	  h1 = h + 1; 
	  if (headerHeight != (h1 + 3)) { 
	    headerHeight = h1 + 3; 
	    changed = true;
	  }
	  cond.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	  //cond.doLayout();
	  cond.positionLabel();
	}
      } else if (blockType == FOR_BLOCK) { 
	float x = x1 + getLeftStubWidth() - STUB_END_SIZE / 2;
	DrawShape init0 = findInRole(INITIALIZATION);
	DrawShape cond0 = findInRole(CONDITION);
	DrawShape incr0 = findInRole(INCREMENTATION);
	DrawShape iter0 = findInRole(ITERABLE);
	BarShape init = null;
	BarShape cond = null;
	BarShape incr = null;
	BarShape iter = null;
	int h = 0;
	if (init0 instanceof BarShape) {
	  init = (BarShape) init0;
	  h = (int) init.getPreferredHeight();
	}
	if (cond0 instanceof BarShape) {
	  cond = (BarShape) cond0;
	  h = Math.max(h, (int) cond.getPreferredHeight());
	}
	if (incr0 instanceof BarShape) {
	  incr = (BarShape) incr0;
	  h = Math.max(h, (int) incr.getPreferredHeight());
	}
	if (iter0 instanceof BarShape) {
	  iter = (BarShape) iter0;
	  h = Math.max(h, (int) iter.getPreferredHeight());
	}
	h1 = h + 1; 
	if (headerHeight != (h1 + 3)) { 
	  headerHeight = h1 + 3; 
	  changed = true;
	}
	if (iter == null) { 
	  // basic for
	  float w = (w1 - rightStubWidth - getLeftStubWidth() + 2 * STUB_END_SIZE ) / 3; 
	  if (init != null) { 
	    init.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	    //init.doLayout();
	    init.positionLabel();
	  }
	  x += (w - STUB_END_SIZE / 2);
	  if (cond != null) { 
	    cond.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	    //cond.doLayout();
	    cond.positionLabel();
	  }
	  x += (w - STUB_END_SIZE / 2);
	  if (incr != null) { 
	    incr.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	    //incr.doLayout();
	    incr.positionLabel();
	  }
	} else { 
	  // enhanced for 
	  float w = (w1 - rightStubWidth - getLeftStubWidth() + 3 * STUB_END_SIZE / 2) / 2; 
	  if (init != null) { 
	    init.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	    //init.doLayout();
	    init.positionLabel();
	  }
	  x += (w - STUB_END_SIZE / 2);
	  iter.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	  //iter.doLayout();
	  iter.positionLabel();
	}	
      } else if (blockType == SWITCH_BLOCK) {
	DrawShape s = findInRole(EXPRESSION);
	if (s instanceof BarShape) { 
	  BarShape exp = (BarShape) s;
	  float w = w1 - rightStubWidth - getLeftStubWidth(); 
	  float x = x1 + getLeftStubWidth() - STUB_END_SIZE / 2;
	  int h = (int) exp.getPreferredHeight();
	  h1 = h + 1; 
	  if (headerHeight != (h1 + 3)) { 
	    headerHeight = h1 + 3; 
	    changed = true;
	  }
	  exp.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	  //exp.doLayout();
	  exp.positionLabel();
	}
      } else if (blockType == CASE_BLOCK) {
	java.util.List<DrawShape> slist = findAllInRole(EXPRESSION);
	if (slist != null && slist.size() > 0) { 
	  int n = slist.size();
	  float w = (w1 - rightStubWidth - getLeftStubWidth()) / n;
	  float x = x1 + getLeftStubWidth() - STUB_END_SIZE / 2;
	  for (int i = 0; i < n; i++) { 
	    DrawShape s = slist.get(i);
	    if (s instanceof BarShape) { 
	      BarShape exp = (BarShape) s;
	      exp.setEnds(x, y1 + 1, x + w - 1, y1 + h1 - 2);
	      exp.positionLabel();
	      x += w; 
	    }
	  } 
	}
      } else if (blockType == CATCH_BLOCK) {
	DrawShape s = findInRole(EXCEPTION);
	if (s instanceof BarShape) { 
	  BarShape exp = (BarShape) s;
	  float x = x1 + getLeftStubWidth() - STUB_END_SIZE / 2;
	  float w = w1 - rightStubWidth - getLeftStubWidth() + STUB_END_SIZE; 
	  exp.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	  exp.positionLabel();
	}

      } else if (blockType == METHOD_BLOCK ||
		 blockType == CONSTRUCTOR_BLOCK) {
	DrawShape name = findInRole(NAME);
	int nw = 10, tw = 10; 
	float x2 = x1 + getLeftStubWidth() + STUB_END_SIZE;
	if (name != null) { 
	  nw = (int) name.getShapeWidth();
	  name.setPosition(x2, y1 + 2);
	}
	DrawShape type = findInRole(TYPE);
	float x3 = x2 + nw + 10; 
	if (type != null) { 
	  type.setPosition(x3, y1 + 2);
	}
      } else if (blockType == CLASS_BLOCK ||
		 blockType == ENUM_BLOCK) { 
	DrawShape name = findInRole(NAME);
	float x2 = x1 + getLeftStubWidth() + STUB_END_SIZE;
	if (name != null) { 
	  name.setPosition(x2, y1 + 2);
	}
      }
    } else if (showFooter) { 		 
      int x1 = (int) left + 1;
      int y1 = (int) (top + height) - footerHeight + 1;
      int w1 = (int) width - 1;
      int h1 = footerHeight - 1;
      if (blockType == DO_WHILE_BLOCK) { 
	DrawShape s = findInRole(CONDITION);
	if (s instanceof BarShape) {
 	  BarShape cond = (BarShape) s;
	  float x = x1 + getLeftStubWidth() - STUB_END_SIZE / 2;
	  float w = w1 - rightStubWidth - getLeftStubWidth() + STUB_END_SIZE; 
	  int h = (int) cond.getPreferredHeight();
	  h1 = h + 1; 
	  if (footerHeight != (h1 + 3)) { 
	    footerHeight = h1 + 3; 
	    changed = true;
	  }
	  cond.setEnds(x, y1 + 1, x + w, y1 + h1 - 2);
	  //cond.doLayout();
	  cond.positionLabel();
	}
      }
    }   
    return changed;
  }

  public Rectangle2D layoutStatement(float top, float left, float width) { 
    return layoutStatement(top, left, width, fitted); 
  }

  public Rectangle2D layoutStatement(float top, float left, float width,
				     boolean fit) { 
    //System.out.println("BlockShape.layoutStatement() " + 
    //top + " " + left + " " + width + " " + closed);

    if (closed) { 
      float h = 10;
      if (showHeader) { 
	h = headerHeight;
      }
      setEnds(left, top, left + width, top + h);
      return bounds; 
    }

    float x = left + GAP;
    float w = width - GAP * 2;
    if (showExpansionBar) { 
      x += expansionBarWidth;
      w -= expansionBarWidth;
    }
    if (showSideBar) { 
      //x += sideBarWidth;
      w -= sideBarWidth;
    }

    float y = top + Math.max(GAP, topMargin);
    if (showHeader) { 
      y += headerHeight; 
    }
    if (subshapes != null) {
      int count = getStatementCount();
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s.isVisible()) { 
	  if (s instanceof StatementShape) {
	    StatementShape stmt = (StatementShape) s;
	    Rectangle2D rect = stmt.layoutStatement(y, x, w);	    
	    y = (float) (rect.getY() + rect.getHeight()) + GAP; 
	    
	    //System.out.println("BlockShape.layoutStatement() subshapes[" + i + "] height= " + 
	    //		       rect.getHeight() + " acceptSubshape=" + isAcceptSubshape() + " " + s);
	  }
	}
      }
      if (count > 0) { 
	y -= GAP; 
      }
      /*
      if (!expanded) {   }
      */
    }  

    float bottom = y + Math.max(GAP, bottomMargin);  
    if (showFooter) { 
      bottom += footerHeight; 
    }

    float minHeight = MIN_HEIGHT + 2 * GAP; 
    if (showHeader) { 
      minHeight += headerHeight; 
    }
    if (showFooter) { 
      minHeight += footerHeight; 
    }
    if (bottom < top + minHeight) { 
      bottom = top + minHeight;
    }

    if (!fit) { 
      float height = (float) Math.abs(p1.getY() - p2.getY());
      if (height > (bottom - top)) {
	bottom = top + height; 
      }
    }

    setEnds(left, top, left + width, bottom);

    return bounds; 
  }

  @Override
  public void setEnds(float x1, float y1, float x2, float y2) {
    super.setEnds(x1, y1, x2, y2);
    positionExpressions();
  }

  @Override
  public boolean isExpanded() { 
    return expanded;
  }

  @Override
  public void expand() { 
    //System.out.println("BlockShape.expand()"); 
    
    super.expand();

    if (!expanded) { 
      if (subshapes != null) {
	int n = subshapes.size();
	for (int i = 0; i < n; i++) {
	  DrawShape s = subshapes.get(i);
	  if (s instanceof StatementShape) {
	    //StatementShape stmt = (StatementShape) s;
	    s.setVisible(true);
	  }
	}
      }
      expanded = true;

      positionSubNodes();    
      adjustParentLayout(); 
    }
  }

  @Override
  public void collapse() { 
    collapse(false); 
  }

  public void collapse(boolean force) { 
    //System.out.println("BlockShape.collapse()"); 

    // do not if no statement or only a single simple statement 
    boolean doCollapse = force; 
    if (!force) { 
      int count = 0; 
      if (subshapes != null) {
	int n = subshapes.size();
	for (int i = 0; i < n; i++) {
	  DrawShape s = subshapes.get(i);
	  if (s instanceof StatementShape) {
	    if (count > 0 || 
		!(s instanceof SimpleStatementShape)) { 
	      doCollapse = true; 
	      break; 
	    } else {
	      count = 1; 
	    }
	  }
	}
      }
    }

    if (doCollapse) { 
      super.collapse();

      if (expanded) { 
	if (subshapes != null) {
	  int n = subshapes.size();
	  for (int i = 0; i < n; i++) {
	    DrawShape s = subshapes.get(i);
	    if (s instanceof StatementShape) {
	      //StatementShape stmt = (StatementShape) s;
	      s.setVisible(false);
	    }
	  }
	}
	expanded = false;
      }
    }
    positionSubNodes(true);
    adjustParentLayout(); 
  }

  public boolean isClosed() { 
    return closed; 
  }

  public boolean isClosable() { 
    return closable; 
  }

  public void open() { 
    if (closed) { 
      closed = false; 
      expand(); 
    }
  }

  public void close() { 
    if (!closed) { 
      closed = true; 
      collapse(true); // force collapse
    }
  }

  public void closeEmptyBlocks() { 
    if (subshapes != null) { 
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s instanceof BlockShape) {
	  BlockShape b = (BlockShape) s;
	  //System.out.println("BlockShape.closeEmptyBlocks() blockType=" + 
	  //	     b.getBlockType() + " count=" + b.getStatementCount());
	  if (b.isClosable()) { 
	    if (b.getStatementCount() == 0) { 
	      b.close();
	    } else { 
	      b.open();
	    }
	  }
	}
      }
    }
  }

  @Override
  public boolean isResizable() {
    return (resizable && parent == null);
  }

  @Override
  public boolean isEdgeSensitive() {
    return (resizable && parent == null);
  }

  @Override
  public boolean isCornorSensitive() {
    return (resizable && parent == null);
  }

  @Override
  public int isInside(float px, float py) {
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    if (showExpansionBar) {
      float y0 = y;
      if (showHeader) { 
	y0 += headerHeight; 
      }
      if (px >= x && 
	  px < (x + expansionBarWidth) && 
	  py >= y0 && 
	  py < (y0 + expansionBarWidth)) { 
	return PointOnIcon;
      } 
    }
    if (showHeader && closable) { 
      float h0 = headerHeight;
      if (px >= x && 
	  px < (x + h0) && 
	  py >= y && 
	  py < (y + h0)) { 
	return PointOnIcon + 1;
      } 
    }
    return super.isInside(px, py); 
  }

  public int getBlockType() { 
    return blockType; 
  }

  public void setBlockType(int blockType) { 
    this.blockType = blockType; 
    switch (blockType) { 
    case WHILE_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = true; 
      headerText = "while";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break; 
    case DO_WHILE_BLOCK: 
      showHeader = false; 
      showFooter = true; 
      showSideBar = true; 
      footerText = "while";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break; 
    case FOR_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = true; 
      headerText = "for";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;
    case IF_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      headerText = "if";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;
    case SWITCH_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      headerText = "switch";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;
    case CASE_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      headerHeight = 18;  
      headerText = "case";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;
    case CASE_DEFAULT:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      headerHeight = 18;  
      headerText = "default";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;

    case IMPORT_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      headerHeight = 14; 
      headerText = "  // import declarations";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;

    case ENUM_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14; 
      headerText = "  // enum declarations";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;

    case METHOD_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      showExpansionBar = false;
      topMargin = 0; 
      bottomMargin = 0; 
      headerText = "\u00ABmethod\u00BB";
      textColor = TextMarker.getColor(TextMarker.STEREOTYPE);
      break;
    case CONSTRUCTOR_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      showExpansionBar = false;
      topMargin = 0; 
      bottomMargin = 0; 
      headerText = "\u00ABconstructor\u00BB";
      textColor = TextMarker.getColor(TextMarker.STEREOTYPE);
      break;

    case PARAMETER_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14; 
      headerText = "// parameters";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case METHOD_PRE:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14; 
      headerText = "// pre-conditions";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case METHOD_POST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14; 
      headerText = "// post-conditions";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case METHOD_BODY:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      headerHeight = 14; 
      headerText = "  // body";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;

    case ATTRIBUTE_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// attributes";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case CONSTANT_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// constants";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case STATIC_FIELD_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// static attributes";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case METHOD_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// methods";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case STATIC_METHOD_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// static methods";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case CONSTRUCTOR_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// constructors";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case INNER_CLASS_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// inner classes";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;

    case ACTION_LIST:
      showHeader = false; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      topMargin = 0; 
      bottomMargin = 0; 
      //headerHeight = 14;  
      //headerText = "// methods";
      //textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;

    case TRUE_BRANCH:
    case FALSE_BRANCH:
      showHeader = false; 
      showFooter = false; 
      showSideBar = true; 
      break; 
    case TRY_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      showExpansionBar = false;
      topMargin = 0; 
      bottomMargin = 0; 
      headerText = "try";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;
    case CATCH_LIST:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      headerHeight = 14; 
      headerText = "  // catch exceptions"; 
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;
    case CATCH_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      headerHeight = 18;  
      headerText = "catch";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;
    case CATCH_FINALLY:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      headerHeight = 18;  
      headerText = "finally";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;
    case CLASS_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      showExpansionBar = false;
      topMargin = 0; 
      bottomMargin = 0; 
      headerText = "class";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;
    case ENUM_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false; 
      closable = true;
      headerText = "enum";
      textColor = TextMarker.getColor(TextMarker.RESERVED_WORD);
      break;

    case INIT_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// initialization of attributes";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;

    case STATIC_INIT_BLOCK:
      showHeader = true; 
      showFooter = false; 
      showSideBar = false;
      closable = true;
      headerHeight = 14;  
      headerText = "// initialization of static attributes";
      textColor = TextMarker.getColor(TextMarker.COMMENT);
      break;

    case DEFAULT_BLOCK:
    default:
      showHeader = false; 
      showFooter = false; 
      showSideBar = false; 
    }
  }

  public void setShowExpansionBar(boolean b) {
    showExpansionBar = b;
  }

  public void setShowSideBar(boolean b) {
    showSideBar = b;
  }

  public void setShowHeader(boolean b) { 
    showHeader = b;
  }

  public void setShowAddButton(boolean b) { 
    showAddButton = b;
  }

  public void setShowFooter(boolean b) { 
    showFooter = b;
  }

  public void setHeaderHeight(int h) { 
    headerHeight = h;
  }	      
 
  public void setFooterHeight(int h) { 
    footerHeight = h; 
  }

  public void setSideBarWidth(int w) { 
    sideBarWidth = w; 
  }

  public void setHeaderText(String headerText) { 
    this.headerText = headerText; 
  }

  public void setFooterText(String footerText) { 
    this.footerText = footerText;
  }

  public void setTopMargin(int m) {
    topMargin = m;
  }

  public void setBottomMargin(int m) {
    bottomMargin = m;
  }

  @Override
  public boolean isAcceptSubshape(DrawShape subshape) {
    if (!acceptSubshape || subshape == null) {
      return false;
    }
    boolean isStmt = false; 
    boolean isInit = false; 
    boolean isParam = false; 
    boolean isAttr = false; 
    boolean isMethodDecl = false; 
    boolean isConstructorDecl = false; 
    boolean isAction = false;
    //boolean isStaticMethodDecl = false; 
    boolean isPredicate = false; 
    boolean isImport = false; 
    boolean isEnumValue = false; 
    boolean isEnumDecl = false; 
    //boolean isConstant = false; 
    //boolean isStaticField = false; 
    if (subshape instanceof StatementShape) {
      StatementShape stmt = (StatementShape) subshape; 
      isEnumDecl = isEnumDecl(stmt); 
      if (isTopLevel(stmt) && !isEnumDecl) {
	return false; 
      }

      if (!isEnumDecl) { 
	isStmt = isStatement(stmt);
	if (!isStmt) {
	  isInit = isInitBlock(stmt);
	}
      }

      if (!isStmt && !isInit && !isEnumDecl && 
	  subshape instanceof SimpleStatementShape) {
	SimpleStatementShape s0 = (SimpleStatementShape) subshape; 
	switch (s0.getStatementType()) { 
	case SimpleStatementShape.PARAMETER:	  
	  isParam = true;
	  break;
	case SimpleStatementShape.ATTRIBUTE:
	case SimpleStatementShape.CONSTANT:
	case SimpleStatementShape.STATIC_FIELD:
	  isAttr = true;
	  break; 
	case SimpleStatementShape.CONSTRUCTOR_DECL:
	  isConstructorDecl = true;
	  break; 
	case SimpleStatementShape.METHOD_DECL:
	case SimpleStatementShape.STATIC_METHOD_DECL:
	  isMethodDecl = true;
	  break;
	case SimpleStatementShape.PREDICATE:
	  isPredicate = true;
	  break;
	case SimpleStatementShape.IMPORT:
	case SimpleStatementShape.STATIC_IMPORT: 
	  isImport = true;
	  break;
	case SimpleStatementShape.ENUMERATION: 
	  isEnumValue = true;
	  break;
	case SimpleStatementShape.ACTION: 
	  isAction = true;
	  break;
	}
      }

      //System.out.println("BlockShape.isAcceptSubshape(): blockType=" + blockType + " isAction=" + isAction);
    
      switch(blockType) { 
      case METHOD_BLOCK:
      case TRY_BLOCK:
      case CLASS_BLOCK:
	return false; 
      case ENUM_BLOCK:
	return isEnumValue;
      case ENUM_LIST:
	return isEnumDecl;
      case PARAMETER_LIST:
	return isParam;
      case CONSTANT_LIST:
	return isAttr; 
      case ATTRIBUTE_LIST:
      case STATIC_FIELD_LIST:
	return isAttr || isInit; 
      case METHOD_LIST:
      case STATIC_METHOD_LIST:
	return isMethodDecl; 
      case CONSTRUCTOR_LIST:
	return isConstructorDecl; 
      case ACTION_LIST:
	return isAction;
      case IMPORT_LIST:
	return isImport; 
      case METHOD_PRE:
      case METHOD_POST:
	return isPredicate; 
      case SWITCH_BLOCK:
	return isCaseBlock(subshape); 
      case CATCH_LIST:
	return isCatchBlock(subshape) || isFinallyBlock(subshape);
      case IF_BLOCK:
	return (getStatementCount() < 2 && isStmt); 
      default:
	return isStmt;
      }
    } else if (subshape instanceof BarShape) { 
      int role = subshape.getRole();
      switch (blockType) { 
      case SWITCH_BLOCK:
      case CASE_BLOCK:
	return  role == EXPRESSION;	
      case CASE_DEFAULT:
      case CATCH_FINALLY:
	return false;
      case CATCH_BLOCK:
	return role == EXCEPTION;
      case IF_BLOCK:
      case WHILE_BLOCK:
      case DO_WHILE_BLOCK:
	return role == CONDITION;
      case FOR_BLOCK:
	return (role == CONDITION || 
		role == ITERABLE ||
		role == INITIALIZATION ||
		role == INCREMENTATION);
      default:
	return false;
      } 
    }

    return false;
  }

  public boolean showCounter() { 
    return false; 
  }

  public AddOption[] getAddOptions() { 
    return addOptions.get(blockType);
  }

  public void setSideBarColor(Color c) {
    sideBarColor = c;
  }

  public void setHeaderColor(Color c) {
    headerColor = c;
  }

  protected Font getHeaderFont() {
    String name = "SansSerif";
    int style = Font.BOLD;
    int size = 12;
    switch (blockType) {
    case IMPORT_LIST:
    case ENUM_LIST:
    case PARAMETER_LIST:
    case ATTRIBUTE_LIST:
    case CONSTANT_LIST:
    case STATIC_FIELD_LIST:
    case METHOD_LIST:
    case STATIC_METHOD_LIST:
    case CONSTRUCTOR_LIST:
    case INNER_CLASS_LIST:
    case METHOD_BODY:
    case METHOD_PRE:
    case METHOD_POST:
    case CATCH_LIST:
    case INIT_BLOCK:
    case STATIC_INIT_BLOCK:
      style = Font.ITALIC;
      size = 11; 
      break;
    case METHOD_BLOCK:
    case CONSTRUCTOR_BLOCK:
    case CASE_BLOCK:
    case CASE_DEFAULT:
    case CATCH_BLOCK:
    case CATCH_FINALLY:
      style = Font.PLAIN;
      size = 11; 
      break;
    default:
    }
    FontMap fmap = FontMap.getInstance();
    int f = fmap.findFont(name, style, size);
    return fmap.getFont(f);     
  }

  protected Color getHeaderColor() { 
    Color c = null;
    switch (blockType) {
    case PARAMETER_LIST:
    case CONSTANT_LIST:
    case STATIC_FIELD_LIST:
    case ATTRIBUTE_LIST:
    case ENUM_LIST:
    case ENUM_BLOCK:
    case CONSTRUCTOR_LIST:
    case METHOD_LIST:
    case STATIC_METHOD_LIST:
    case CASE_BLOCK:
    case CASE_DEFAULT:
      if (attr != null) {
	c = attr.getPenColor(); 
      }
      break;
    case METHOD_BLOCK:
    case CONSTRUCTOR_BLOCK:
    case CLASS_BLOCK:
    case INNER_CLASS_LIST:
      return new Color(0, 128, 0, 64);
    case IMPORT_LIST:
      return new Color(0xEE, 0x82, 0xEE, 64);
    case DEFAULT_BLOCK:
    default:
    }
    if (c != null) { 
      return new Color(c.getRed(), c.getGreen(), c.getBlue(), 64);
    } else { 
      return headerColor; 
    }
  }

  protected Color getSideBarColor() { 
    switch (blockType) {
    case TRUE_BRANCH:
    case WHILE_BLOCK:
    case DO_WHILE_BLOCK:
    case FOR_BLOCK:
      return new Color(0, 255, 0, 64);
    case FALSE_BRANCH:
      return new Color(255, 0, 0, 64);
    default:
      return sideBarColor;
    }
  }

  protected Color getTextColor() { 
    return textColor; 
  }

  protected DrawShape findDefaultCaseBlock() { 
    if (subshapes != null) { 
      for (DrawShape s : subshapes) { 
	if (isDefaultCaseBlock(s)) { 
	  return s;
	}
      } 
    }
    return null;
  }

  protected DrawShape findFinallyBlock() { 
    if (subshapes != null) { 
      for (DrawShape s : subshapes) { 
	if (isFinallyBlock(s)) { 
	  return s;
	}
      } 
    }
    return null;
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("blockType", blockType);
    attrs.put("expanded", expanded);
    attrs.put("showExpansionBar", showExpansionBar);
    attrs.put("showSideBar", showSideBar);
    attrs.put("showHeader", showHeader);
    attrs.put("showFooter", showFooter);
    attrs.put("showAdd", showAddButton);
    attrs.put("headerText", headerText); 
    attrs.put("footerText", footerText);
    attrs.put("closable", closable); 
    attrs.put("closed", closed);
    attrs.put("topMargin", topMargin);
    attrs.put("bottomMargin", bottomMargin);
    attrs.put("headerHeight", headerHeight);
    attrs.put("footerHeight", footerHeight);
    attrs.put("sideBarWidth", sideBarWidth);

    if (modifiers != 0) { 
      attrs.put("modifiers", modifiers);
    }
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("blockType");
      try {
	blockType = Integer.parseInt(s);
	setBlockType(blockType);
      } catch (NumberFormatException ex) {
      }

      s = node.getAttribute("expanded");
      if (s != null && s.length() > 0) { 
	expanded = Boolean.valueOf(s);
      }
      s = node.getAttribute("showExpansionBar");
      if (s != null && s.length() > 0) { 
	showExpansionBar = Boolean.valueOf(s);
      }

      s = node.getAttribute("showSideBar");
      if (s != null && s.length() > 0) { 
	showSideBar = Boolean.valueOf(s);
      }
      s = node.getAttribute("showHeader");
      if (s != null && s.length() > 0) { 
	showHeader = Boolean.valueOf(s);
      }
      s = node.getAttribute("showFooter");
      if (s != null && s.length() > 0) { 
	showFooter = Boolean.valueOf(s);
      }
      s = node.getAttribute("showAdd");
      if (s != null && s.length() > 0) { 
	showAddButton = Boolean.valueOf(s);
      }

      s = node.getAttribute("closable");
      if (s != null && s.length() > 0) { 
	closable = Boolean.valueOf(s);
      }
      s = node.getAttribute("closed");
      if (s != null && s.length() > 0) { 
	closed = Boolean.valueOf(s);
      }

      s = node.getAttribute("topMargin");
      try {
	topMargin = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }
      s = node.getAttribute("bottomMargin");
      try {
	bottomMargin = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }

      s = node.getAttribute("headerHeight");
      try {
	headerHeight = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }
      s = node.getAttribute("footerHeight");
      try {
	footerHeight = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }
      s = node.getAttribute("sideBarWidth");
      try {
	sideBarWidth = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }

      s = node.getAttribute("modifiers");
      try {
	modifiers = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }

      headerText = node.getAttribute("headerText");
      footerText = node.getAttribute("footerText");
    }
  }

  protected int getLeftStubWidth() { 
    switch(blockType) {
    case SWITCH_BLOCK:
      return 60; 
    case CASE_BLOCK:
      return 50;
    case FOR_BLOCK: 
      return 30;
    case IF_BLOCK:
      return 30;
    case METHOD_BLOCK:
      return 70;
    case CONSTRUCTOR_BLOCK:
      return 90;

    case WHILE_BLOCK:
    case DO_WHILE_BLOCK:

    case PARAMETER_LIST:
    case ATTRIBUTE_LIST:
    case METHOD_LIST:
    default:
      return leftStubWidth;
    }
  }

  public void addBlocks(BlockShape[] blocks) { 
    if (blocks != null) { 
      for (int i = 0; i < blocks.length; i++) { 
	if (blocks[i] != null) { 
	  blocks[i].setMovable(false);
	  addShape(blocks[i]);
	}
      }
      layoutComponents();
    }
  }

  public void showBlock(int type) { 
    BlockShape b = BlockShapeUtil.findSubBlockOfType(this, type);
    if (b != null && 
	!b.isVisible()) {
      b.setVisible(true);
      layoutComponents();
    }
  }

  public void hideBlock(int type) { 
    BlockShape b = BlockShapeUtil.findSubBlockOfType(this, type);
    if (b != null && 
	b.isVisible()) {
      b.setVisible(false);
      layoutComponents();
    }
  }

  protected Shape makeLeftStub(float x, float y, float w, float h) { 
    int width = getLeftStubWidth();
    GeneralPath polygon = new GeneralPath();
    polygon.moveTo(x, y); 
    switch(blockType) {
    case FOR_BLOCK: 
      polygon.lineTo(x + width, y); 
      polygon.lineTo(x + width - STUB_END_SIZE, y + h); 
      break;
    case METHOD_BLOCK:
      width = 16;
      polygon.lineTo(x + width, y); 
      polygon.quadTo(x + width - STUB_END_SIZE - 2, y + h / 2, x + width, y + h); 
      break;
    case CATCH_BLOCK:
      polygon.lineTo(x + width - STUB_END_SIZE, y); 
      polygon.lineTo(x + width, y + h / 2); 
      polygon.lineTo(x + width - STUB_END_SIZE, y + h);
      break; 
    case SWITCH_BLOCK:
    case CASE_BLOCK:
    case IF_BLOCK:
    case WHILE_BLOCK:
    case DO_WHILE_BLOCK:
    default:
      polygon.lineTo(x + width, y); 
      polygon.lineTo(x + width - STUB_END_SIZE, y + h / 2); 
      polygon.lineTo(x + width, y + h); 
    }
    polygon.lineTo(x, y + h);
    polygon.closePath();
    return polygon;
  }

  protected Shape makeRightStub(float x, float y, float w, float h) { 
    int width = rightStubWidth;
    GeneralPath polygon = new GeneralPath();
    polygon.moveTo(x + w, y); 
    switch(blockType) {
    case FOR_BLOCK: 
      polygon.lineTo(x + w - width + STUB_END_SIZE, y); 
      polygon.lineTo(x + w - width, y + h / 2); 
      polygon.lineTo(x + w - width + STUB_END_SIZE, y + h); 
      break;
    case METHOD_BLOCK:
      width += (60 - 16); 
      polygon.lineTo(x + w - width, y); 
      polygon.quadTo(x + w - width + STUB_END_SIZE + 2, y + h / 2,
		     x + w - width, y + h); 
      break; 
    case SWITCH_BLOCK:
    case CASE_BLOCK:
      polygon.lineTo(x + w - width, y); 
      polygon.lineTo(x + w - width, y + h);
      break;
    case IF_BLOCK:
    case WHILE_BLOCK:
    case DO_WHILE_BLOCK:
    default:
      polygon.lineTo(x + w - width, y); 
      polygon.lineTo(x + w - width + STUB_END_SIZE, y + h / 2); 
      polygon.lineTo(x + w - width, y + h); 
    }
    polygon.lineTo(x + w, y + h);
    polygon.closePath();
    return polygon;
  }

  protected String getExpressionText(int role) {
    DrawShape s = findInRole(role); 
    if (s != null) {
      return getExpressionText(s);
    }
    return null;
  }

  static public String getExpressionText(DrawShape s) { 
    if (s != null) { 
      DrawShape label = s.findInRole(DrawShapeConstants.ROLE_NODE_LABEL);
      if (label instanceof TextShape) { 
	TextShape tshape = (TextShape) label;
	return tshape.getText();
      }
    }
    return null;
  }

  static protected boolean isCaseBlock(DrawShape shape) { 
    if (shape instanceof BlockShape) { 
      BlockShape b = (BlockShape) shape;
      int type = b.getBlockType();
      return (type == CASE_BLOCK || type == CASE_DEFAULT);
    }
    return false; 
  }

  static protected boolean isDefaultCaseBlock(DrawShape shape) { 
    if (shape instanceof BlockShape) { 
      BlockShape b = (BlockShape) shape;
      int type = b.getBlockType();
      return (type == CASE_DEFAULT);
    }
    return false; 
  }

  static protected boolean isCatchBlock(DrawShape shape) { 
    if (shape instanceof BlockShape) { 
      BlockShape b = (BlockShape) shape;
      int type = b.getBlockType();
      return (type == CATCH_BLOCK);
    }
    return false; 
  }

  static protected boolean isFinallyBlock(DrawShape shape) { 
    if (shape instanceof BlockShape) { 
      BlockShape b = (BlockShape) shape;
      int type = b.getBlockType();
      return (type == CATCH_FINALLY);
    }
    return false; 
  }

  static protected boolean isStatement(StatementShape shape) {
    if (shape != null) {
      if (shape instanceof BlockShape) { 
	BlockShape b = (BlockShape) shape; 
	int type = b.getBlockType();
	return (type == BlockShape.DEFAULT_BLOCK ||
		type == BlockShape.WHILE_BLOCK ||
		type == BlockShape.DO_WHILE_BLOCK ||
		type == BlockShape.FOR_BLOCK ||
		type == BlockShape.IF_BLOCK ||
		type == BlockShape.SWITCH_BLOCK ||
		type == BlockShape.TRY_BLOCK);
      } else if (shape instanceof SimpleStatementShape) { 
	SimpleStatementShape s = (SimpleStatementShape) shape;
	int type = s.getStatementType();
	return (type == SimpleStatementShape.SIMPLE_STATEMENT ||
		type == SimpleStatementShape.VARIABLE_DECL ||
		type == SimpleStatementShape.ASSIGNMENT ||
		type == SimpleStatementShape.METHOD_CALL ||
		type == SimpleStatementShape.INPUT ||
		type == SimpleStatementShape.OUTPUT  ||
		type == SimpleStatementShape.COMMENT); 
      }
    }
    return false; 
  }

  static protected boolean isInitBlock(StatementShape shape) {
    if (shape instanceof BlockShape) { 
      BlockShape b = (BlockShape) shape; 
      int type = b.getBlockType();
      return (type == BlockShape.DEFAULT_BLOCK ||
	      type == BlockShape.INIT_BLOCK ||
	      type == BlockShape.STATIC_INIT_BLOCK);
    }
    return false; 
  }

  static protected boolean isEnumDecl(StatementShape shape) {
    if (shape instanceof BlockShape) { 
      BlockShape b = (BlockShape) shape; 
      int type = b.getBlockType();
      return (type == BlockShape.ENUM_BLOCK);
    }
    return false; 
  }

  static protected boolean isTopLevel(StatementShape shape) {
    if (shape != null) {
      if (shape instanceof BlockShape) { 
	BlockShape b = (BlockShape) shape; 
	int type = b.getBlockType();
	return (type == BlockShape.IMPORT_LIST ||
		type == BlockShape.CLASS_BLOCK ||
		type == BlockShape.ENUM_BLOCK ||
		type == BlockShape.METHOD_BLOCK);
      } else if (shape instanceof SimpleStatementShape) { 
	return false; 
      }
    }
    return false; 
  }

  static public void putAddOption(int blockType, AddOption[] opt) { 
    addOptions.put(blockType, opt);
  }

  static Map<Integer, AddOption[]> addOptions = new HashMap<Integer, AddOption[]>(); 

  static public class AddOption {
    public AddOption(StatementShape proto,
		     String text) { 
      AddOption.this.proto = proto;
      AddOption.this.text = text; 
    }
    StatementShape proto;
    String text; 
  }

  protected int blockType = DEFAULT_BLOCK; 

  protected int topMargin = 10; 
  protected int bottomMargin = 10; 

  protected int modifiers = 0; 

  protected boolean expanded = true;
  protected boolean showExpansionBar = true; 
  protected int expansionBarWidth = 10; 

  protected boolean showAddButton = false; 

  protected boolean closable = false; 
  protected boolean closed   = false; 

  protected boolean showSideBar = false; 
  protected boolean showHeader = false;
  protected boolean showFooter = false;
  
  protected int sideBarWidth = 10; 
  protected int headerHeight = 20; 
  protected int footerHeight = 20; 

  protected Color sideBarColor = new Color(0, 0, 128, 64); 
  protected Color headerColor  = new Color(255, 128, 0, 64); 
  protected Color footerColor  = new Color(255, 128, 0, 64); 
  protected Color textColor = Color.black;

  protected String headerText = null;
  protected String footerText = null;

  protected int leftStubWidth  = 50; 
  protected int rightStubWidth = 16; 

  protected static final int STUB_END_SIZE = 8; 

  protected static final int MIN_HEIGHT = 20; 
  protected static final int MIN_WIDTH  = 40; 
  protected static final int GAP = 3; // the vertical gap between statements, min margin on all sides 

  //
  // Utility 
  //

  static protected void drawShape3(Shape shape, Graphics2D g2) { 
    if (g2 != null && shape != null) {
      Rectangle2D rect = shape.getBounds2D();
      int width = (int) rect.getWidth() + 10;
      int height = (int) rect.getHeight() + 10;
      int x = (int) rect.getX();
      int y = (int) rect.getY();

      AffineTransform af = new AffineTransform();
      af.setToTranslation(-x + 4, -y + 4);
      Shape shape1 = af.createTransformedShape(shape);
      BufferedImage bi = new BufferedImage(width, height, Transparency.TRANSLUCENT);     
      Graphics2D big = bi.createGraphics();
      big.setColor(g2.getColor());
      big.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
			   RenderingHints.VALUE_ANTIALIAS_ON);
      af.setToTranslation(-2, -2);

      big.setComposite(AlphaComposite.Src);
      big.draw(shape1);  
      big.setComposite(AlphaComposite.Clear);
      big.setColor(new Color(0, 0, 0, 0));
      shape1 = af.createTransformedShape(shape1);
      big.fill(shape1);

      big.setComposite(AlphaComposite.Src);
      big.setColor(g2.getColor());
      big.draw(shape1); 

      big.setComposite(AlphaComposite.Clear);
      big.setColor(new Color(0, 0, 0, 0));
      shape1 = af.createTransformedShape(shape1);
      big.fill(shape1);

      big.setComposite(AlphaComposite.Src);
      big.setColor(g2.getColor());
      big.draw(shape1); 

      g2.drawImage(bi, x - 4, y - 4, null);
    }
  }

  ////////////////////////////////////////////
  // Debugging 
  ////////////////////////////////////////////

  private void walkSubshapes() { 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	System.out.println("== BlockShape subshapes[" + i + "] isSubShapeClipped=" + 
			   isSubShapeClipped(s)); 
	
      }
    }
  }

}



